Implement "cargo init"
authorVitaly _Vi Shukela <vi0oss@gmail.com>
Sat, 23 Jan 2016 12:48:59 +0000 (15:48 +0300)
committerVitaly _Vi Shukela <vi0oss@gmail.com>
Sat, 23 Jan 2016 12:48:59 +0000 (15:48 +0300)
When non-existing directory is provided as argument, it
works just like "cargo new".

When existing directory is used, it may also create template
source file like "cargo new" or may find and use existing
source code for Cargo.toml.

Squashed commit of the following:

    cargo init: Supply USER envvar for one test
    cargo init: Other message when Cargo.toml already exists
    cargo init: Resolve conflict after with #2257
    fix minor issues
    cargo new/init: Simplify error handling code in entry points
    cargo new/init: Better message for invalid characters in name
    cargo init: fix minor issues in test
    cargo init: Avoid excessive builds in the test
    cargo init: minor fixes
    cargo init: Skip no_filename test on Windows
    cargo init: Implement better error message for bin name clash
    cargo init: minor fixes
    cargo init: handle "/" path
    cargo init: Actualise
    cargo new: Fix upper case error message in test
    cargo init: Remove paths::{file,directory}_already_exists
    fix uppper-case error messages
    cargo init: Fix minor issues per diff comments
    cargo init: Change binary handling
    cargo init: Move multiple lib error detection away from mk
    cargo init: Support optional path argument
    cargo init: Fix minor issues per Github comments
    cargo init: Fix complaint from tests/check-style.sh
    cargo init: Handle projects with multiple mains
    cargo init: Major refactor, multi-target projects
    cargo init: Add Cargo.lock unconditionally
    cargo init: Fix complains from tests/check-style.sh
    cargo init: Tests for handling VCS
    cargo init: Handle VCS
    cargo init: work in progress
    cargo init: Deduplicate some things between new and init
    cargo init: Auto-detection of --bin
    cargo init: work in progress...
    cargo init: Fix tests and allow explicit --vcs
    cargo init: intermediate refactor
    cargo init: First sketch of implementation
    cargo init: Preliminary test
    cargo init: first stub

See
https://github.com/vi/cargo/tree/cargo_init_unsquashed
for individual commits

src/bin/cargo.rs
src/bin/init.rs [new file with mode: 0644]
src/cargo/ops/cargo_new.rs
src/cargo/ops/mod.rs
src/cargo/util/paths.rs
tests/test_cargo_init.rs [new file with mode: 0644]
tests/test_cargo_new.rs
tests/tests.rs

index 72db84d251d7296becbb8007330cb2eb1f094634..62556279d59235bba76866501f4a50ddf7cd3f4c 100644 (file)
@@ -69,6 +69,7 @@ macro_rules! each_subcommand{
         $mac!(generate_lockfile);
         $mac!(git_checkout);
         $mac!(help);
+        $mac!(init);
         $mac!(install);
         $mac!(locate_project);
         $mac!(login);
diff --git a/src/bin/init.rs b/src/bin/init.rs
new file mode 100644 (file)
index 0000000..c5e4cb6
--- /dev/null
@@ -0,0 +1,53 @@
+use std::env;
+
+use cargo::ops;
+use cargo::util::{CliResult, Config};
+
+#[derive(RustcDecodable)]
+struct Options {
+    flag_verbose: bool,
+    flag_quiet: bool,
+    flag_color: Option<String>,
+    flag_bin: bool,
+    arg_path: Option<String>,
+    flag_name: Option<String>,
+    flag_vcs: Option<ops::VersionControl>,
+}
+
+pub const USAGE: &'static str = "
+Create a new cargo package in current directory
+
+Usage:
+    cargo init [options] [<path>]
+    cargo init -h | --help
+
+Options:
+    -h, --help          Print this message
+    --vcs VCS           Initialize a new repository for the given version
+                        control system (git or hg) or do not initialize any version
+                        control at all (none) overriding a global configuration.
+    --bin               Use a binary instead of a library template
+    --name NAME         Set the resulting package name
+    -v, --verbose       Use verbose output
+    -q, --quiet         No output printed to stdout
+    --color WHEN        Coloring: auto, always, never
+";
+
+pub fn execute(options: Options, config: &Config) -> CliResult<Option<()>> {
+    debug!("executing; cmd=cargo-init; args={:?}", env::args().collect::<Vec<_>>());
+    try!(config.shell().set_verbosity(options.flag_verbose, options.flag_quiet));
+    try!(config.shell().set_color_config(options.flag_color.as_ref().map(|s| &s[..])));
+
+    let Options { flag_bin, arg_path, flag_name, flag_vcs, .. } = options;
+
+    let opts = ops::NewOptions {
+        version_control: flag_vcs,
+        bin: flag_bin,
+        path: &arg_path.unwrap_or(format!(".")),
+        name: flag_name.as_ref().map(|s| s.as_ref()),
+    };
+
+    try!(ops::init(opts, config));
+    Ok(None)
+}
+
index 18cfeb88ff2c615bd311e3b5a45de549466e103d..a855403d1eeefc15d57f2d84908b78843f4a6488 100644 (file)
@@ -2,6 +2,7 @@ use std::env;
 use std::fs;
 use std::io::prelude::*;
 use std::path::Path;
+use std::collections::BTreeMap;
 
 use rustc_serialize::{Decodable, Decoder};
 
@@ -24,6 +25,19 @@ pub struct NewOptions<'a> {
     pub name: Option<&'a str>,
 }
 
+struct SourceFileInformation {
+    relative_path: String,
+    target_name: String,
+    bin: bool,
+}
+
+struct MkOptions<'a> {
+    version_control: Option<VersionControl>,
+    path: &'a Path,
+    name: &'a str,
+    source_files: Vec<SourceFileInformation>,
+}
+
 impl Decodable for VersionControl {
     fn decode<D: Decoder>(d: &mut D) -> Result<VersionControl, D::Error> {
         Ok(match &try!(d.read_str())[..] {
@@ -44,38 +58,234 @@ struct CargoNewConfig {
     version_control: Option<VersionControl>,
 }
 
-pub fn new(opts: NewOptions, config: &Config) -> CargoResult<()> {
-    let path = config.cwd().join(opts.path);
-    if fs::metadata(&path).is_ok() {
-        bail!("destination `{}` already exists", path.display())
+fn get_name<'a>(path: &'a Path, opts: &'a NewOptions, config: &Config) -> CargoResult<&'a str> {
+    if let Some(name) = opts.name {
+        return Ok(name);
     }
-    let name = match opts.name {
-        Some(name) => name,
-        None => {
-            let dir_name = try!(path.file_name().and_then(|s| s.to_str()).chain_error(|| {
-                human(&format!("cannot create a project with a non-unicode name: {:?}",
-                               path.file_name().unwrap()))
-            }));
-            if opts.bin {
-                dir_name
-            } else {
-                let new_name = strip_rust_affixes(dir_name);
-                if new_name != dir_name {
-                    let message = format!(
-                        "note: package will be named `{}`; use --name to override",
-                        new_name);
-                    try!(config.shell().say(&message, BLACK));
-                }
-                new_name
-            }
+    
+    if path.file_name().is_none() {
+        bail!("cannot auto-detect project name from path {:?} ; use --name to override",
+                              path.as_os_str());
+    }
+    
+    let dir_name = try!(path.file_name().and_then(|s| s.to_str()).chain_error(|| {
+        human(&format!("cannot create a project with a non-unicode name: {:?}",
+                       path.file_name().unwrap()))
+    }));
+    
+    if opts.bin {
+        Ok(dir_name)
+    } else {
+        let new_name = strip_rust_affixes(dir_name);
+        if new_name != dir_name {
+            let message = format!(
+                "note: package will be named `{}`; use --name to override",
+                new_name);
+            try!(config.shell().say(&message, BLACK));
         }
-    };
+        Ok(new_name)
+    }
+}
+
+fn check_name(name: &str) -> CargoResult<()> {
     for c in name.chars() {
         if c.is_alphanumeric() { continue }
         if c == '_' || c == '-' { continue }
-        bail!("Invalid character `{}` in crate name: `{}`", c, name)
+        bail!("Invalid character `{}` in crate name: `{}`\n\
+               use --name to override crate name",
+              c, name)
+    }
+    Ok(())
+}
+
+fn detect_source_paths_and_types(project_path : &Path, 
+                                 project_name: &str, 
+                                 detected_files: &mut Vec<SourceFileInformation>,
+                                 ) -> CargoResult<()> {
+    let path = project_path;
+    let name = project_name;
+    
+    enum H {
+        Bin,
+        Lib,
+        Detect,
+    }
+    
+    struct Test {
+        proposed_path: String,
+        handling: H,
+    }
+        
+    let tests = vec![
+        Test { proposed_path: format!("src/main.rs"),     handling: H::Bin },
+        Test { proposed_path: format!("main.rs"),         handling: H::Bin },
+        Test { proposed_path: format!("src/{}.rs", name), handling: H::Detect },
+        Test { proposed_path: format!("{}.rs", name),     handling: H::Detect },
+        Test { proposed_path: format!("src/lib.rs"),      handling: H::Lib },
+        Test { proposed_path: format!("lib.rs"),          handling: H::Lib },
+    ];
+    
+    for i in tests {
+        let pp = i.proposed_path;
+        
+        // path/pp does not exist or is not a file
+        if !fs::metadata(&path.join(&pp)).map(|x| x.is_file()).unwrap_or(false) {
+            continue;
+        }
+        
+        let sfi = match i.handling {
+            H::Bin => {
+                SourceFileInformation { 
+                    relative_path: pp, 
+                    target_name: project_name.to_string(), 
+                    bin: true 
+                }
+            }
+            H::Lib => {
+                SourceFileInformation { 
+                    relative_path: pp, 
+                    target_name: project_name.to_string(), 
+                    bin: false
+                }
+            }
+            H::Detect => {
+                let content = try!(paths::read(&path.join(pp.clone())));
+                let isbin = content.contains("fn main");
+                SourceFileInformation { 
+                    relative_path: pp, 
+                    target_name: project_name.to_string(), 
+                    bin: isbin 
+                }
+            }
+        };
+        detected_files.push(sfi);
+    }
+    
+    // Check for duplicate lib attempt
+    
+    let mut previous_lib_relpath : Option<&str> = None;
+    let mut duplicates_checker : BTreeMap<&str, &SourceFileInformation> = BTreeMap::new();
+        
+    for i in detected_files {
+        if i.bin {
+            if let Some(x) = BTreeMap::get::<str>(&duplicates_checker, i.target_name.as_ref()) {
+                bail!("\
+multiple possible binary sources found:
+  {}
+  {}
+cannot automatically generate Cargo.toml as the main target would be ambiguous",
+                      &x.relative_path, &i.relative_path);
+            }
+            duplicates_checker.insert(i.target_name.as_ref(), i);
+        } else {
+            if let Some(plp) = previous_lib_relpath {
+                return Err(human(format!("cannot have a project with \
+                                         multiple libraries, \
+                                         found both `{}` and `{}`",
+                                         plp, i.relative_path)));
+            }
+            previous_lib_relpath = Some(&i.relative_path);
+        }
+    }
+    
+    Ok(())
+}
+
+fn plan_new_source_file(bin: bool, project_name: String) -> SourceFileInformation {
+    if bin {
+        SourceFileInformation { 
+             relative_path: "src/main.rs".to_string(),
+             target_name: project_name,
+             bin: true,
+        }
+    } else {
+        SourceFileInformation {
+             relative_path: "src/lib.rs".to_string(),
+             target_name: project_name,
+             bin: false,
+        }
     }
-    mk(config, &path, name, &opts).chain_error(|| {
+}
+
+pub fn new(opts: NewOptions, config: &Config) -> CargoResult<()> {
+    let path = config.cwd().join(opts.path);
+    if fs::metadata(&path).is_ok() {
+        bail!("destination `{}` already exists",
+              path.display())
+    }
+    
+    let name = try!(get_name(&path, &opts, config));
+    try!(check_name(name));
+
+    let mkopts = MkOptions {
+        version_control: opts.version_control,
+        path: &path,
+        name: name,
+        source_files: vec![plan_new_source_file(opts.bin, name.to_string())],
+    };
+    
+    mk(config, &mkopts).chain_error(|| {
+        human(format!("Failed to create project `{}` at `{}`",
+                      name, path.display()))
+    })
+}
+
+pub fn init(opts: NewOptions, config: &Config) -> CargoResult<()> {
+    let path = config.cwd().join(opts.path);
+    
+    let cargotoml_path = path.join("Cargo.toml");
+    if fs::metadata(&cargotoml_path).is_ok() {
+        bail!("`cargo init` cannot be run on existing Cargo projects")
+    }
+    
+    let name = try!(get_name(&path, &opts, config));
+    try!(check_name(name));
+    
+    let mut src_paths_types = vec![];
+    
+    try!(detect_source_paths_and_types(&path, name, &mut src_paths_types));
+    
+    if src_paths_types.len() == 0 {
+        src_paths_types.push(plan_new_source_file(opts.bin, name.to_string()));
+    } else {
+        // --bin option may be ignored if lib.rs or src/lib.rs present
+        // Maybe when doing `cargo init --bin` inside a library project stub,
+        // user may mean "initialize for library, but also add binary target"
+    }
+    
+    let mut version_control = opts.version_control;
+    
+    if version_control == None {
+        let mut num_detected_vsces = 0;
+        
+        if fs::metadata(&path.join(".git")).is_ok() {
+            version_control = Some(VersionControl::Git);
+            num_detected_vsces += 1;
+        }
+        
+        if fs::metadata(&path.join(".hg")).is_ok() {
+            version_control = Some(VersionControl::Hg);
+            num_detected_vsces += 1;
+        }
+        
+        // if none exists, maybe create git, like in `cargo new`
+        
+        if num_detected_vsces > 1 {
+            bail!("both .git and .hg directories found \
+                              and the ignore file can't be \
+                              filled in as a result, \
+                              specify --vcs to override detection");
+        }
+    }
+    
+    let mkopts = MkOptions {
+        version_control: version_control,
+        path: &path,
+        name: name,
+        source_files: src_paths_types,
+    };
+    
+    mk(config, &mkopts).chain_error(|| {
         human(format!("Failed to create project `{}` at `{}`",
                       name, path.display()))
     })
@@ -99,14 +309,13 @@ fn existing_vcs_repo(path: &Path, cwd: &Path) -> bool {
     GitRepo::discover(path, cwd).is_ok() || HgRepo::discover(path, cwd).is_ok()
 }
 
-fn mk(config: &Config, path: &Path, name: &str,
-      opts: &NewOptions) -> CargoResult<()> {
+fn mk(config: &Config, opts: &MkOptions) -> CargoResult<()> {
+    let path = opts.path;
+    let name = opts.name;
     let cfg = try!(global_config(config));
     let mut ignore = "target\n".to_string();
     let in_existing_vcs_repo = existing_vcs_repo(path.parent().unwrap(), config.cwd());
-    if !opts.bin {
-        ignore.push_str("Cargo.lock\n");
-    }
+    ignore.push_str("Cargo.lock\n");
 
     let vcs = match (opts.version_control, cfg.version_control, in_existing_vcs_repo) {
         (None, None, false) => VersionControl::Git,
@@ -117,15 +326,19 @@ fn mk(config: &Config, path: &Path, name: &str,
 
     match vcs {
         VersionControl::Git => {
-            try!(GitRepo::init(path, config.cwd()));
-            try!(paths::write(&path.join(".gitignore"), ignore.as_bytes()));
+            if !fs::metadata(&path.join(".git")).is_ok() {
+                try!(GitRepo::init(path, config.cwd()));
+            }
+            try!(paths::append(&path.join(".gitignore"), ignore.as_bytes()));
         },
         VersionControl::Hg => {
-            try!(HgRepo::init(path, config.cwd()));
-            try!(paths::write(&path.join(".hgignore"), ignore.as_bytes()));
+            if !fs::metadata(&path.join(".hg")).is_ok() {
+                try!(HgRepo::init(path, config.cwd()));
+            }
+            try!(paths::append(&path.join(".hgignore"), ignore.as_bytes()));
         },
         VersionControl::NoVcs => {
-            try!(fs::create_dir(path));
+            try!(fs::create_dir_all(path));
         },
     };
 
@@ -139,6 +352,32 @@ fn mk(config: &Config, path: &Path, name: &str,
         (Some(name), None, _, None) |
         (None, None, name, None) => name,
     };
+    
+    let mut cargotoml_path_specifier = String::new();
+    
+    // Calculare what [lib] and [[bin]]s do we need to append to Cargo.toml
+    
+    for i in &opts.source_files {
+        if i.bin {
+            if i.relative_path != "src/main.rs" {
+                cargotoml_path_specifier.push_str(&format!(r#"
+[[bin]]
+name = "{}"
+path = {}
+"#, i.target_name, toml::Value::String(i.relative_path.clone())));
+            }
+        } else {
+            if i.relative_path != "src/lib.rs" {
+                cargotoml_path_specifier.push_str(&format!(r#"
+[lib]
+name = "{}"
+path = {}
+"#, i.target_name, toml::Value::String(i.relative_path.clone())));
+            }
+        }
+    }
+
+    // Create Cargo.toml file with necessary [lib] and [[bin]] sections, if needed
 
     try!(paths::write(&path.join("Cargo.toml"), format!(
 r#"[package]
@@ -147,25 +386,40 @@ version = "0.1.0"
 authors = [{}]
 
 [dependencies]
-"#, name, toml::Value::String(author)).as_bytes()));
+{}"#, name, toml::Value::String(author), cargotoml_path_specifier).as_bytes()));
 
-    try!(fs::create_dir(&path.join("src")));
 
-    if opts.bin {
-        try!(paths::write(&path.join("src/main.rs"), b"\
+    // Create all specified source files 
+    // (with respective parent directories) 
+    // if they are don't exist
+
+    for i in &opts.source_files {
+        let path_of_source_file = path.join(i.relative_path.clone());
+        
+        if let Some(src_dir) = path_of_source_file.parent() {
+            try!(fs::create_dir_all(src_dir));
+        }
+    
+        let default_file_content : &[u8] = if i.bin {
+            b"\
 fn main() {
     println!(\"Hello, world!\");
 }
-"));
-    } else {
-        try!(paths::write(&path.join("src/lib.rs"), b"\
+"
+        } else {
+            b"\
 #[cfg(test)]
 mod test {
     #[test]
     fn it_works() {
     }
 }
-"));
+"
+        };
+    
+        if !fs::metadata(&path_of_source_file).map(|x| x.is_file()).unwrap_or(false) {
+            return paths::write(&path_of_source_file, default_file_content)
+        }
     }
 
     Ok(())
index c8924833aa9e0bd0deef099a50ffb64cf237c2ce..806d3921a6e7913a5f7a615bc5522bd15bebcc1f 100644 (file)
@@ -8,7 +8,7 @@ pub use self::cargo_rustc::{BuildOutput, BuildConfig, TargetConfig};
 pub use self::cargo_rustc::{CommandType, CommandPrototype, ExecEngine, ProcessEngine};
 pub use self::cargo_run::run;
 pub use self::cargo_install::{install, install_list, uninstall};
-pub use self::cargo_new::{new, NewOptions, VersionControl};
+pub use self::cargo_new::{new, init, NewOptions, VersionControl};
 pub use self::cargo_doc::{doc, DocOptions};
 pub use self::cargo_generate_lockfile::{generate_lockfile};
 pub use self::cargo_generate_lockfile::{update_lockfile};
index 1f08ea6dd7d3dbd9c019a69a3eb4c8c7f7089fc0..2e9397df6c449b6b2cc4ef3dda1a9c19f46b94cd 100644 (file)
@@ -1,6 +1,7 @@
 use std::env;
 use std::ffi::{OsStr, OsString};
 use std::fs::File;
+use std::fs::OpenOptions;
 use std::io::prelude::*;
 use std::path::{Path, PathBuf, Component};
 
@@ -87,6 +88,21 @@ pub fn write(path: &Path, contents: &[u8]) -> CargoResult<()> {
     })
 }
 
+pub fn append(path: &Path, contents: &[u8]) -> CargoResult<()> {
+    (|| -> CargoResult<()> {
+        let mut f = try!(OpenOptions::new()
+                            .write(true)
+                            .append(true)
+                            .create(true)
+                            .open(path));
+
+        try!(f.write_all(contents));
+        Ok(())
+    }).chain_error(|| {
+        internal(format!("failed to write `{}`", path.display()))
+    })
+}
+
 #[cfg(unix)]
 pub fn path2bytes(path: &Path) -> CargoResult<&[u8]> {
     use std::os::unix::prelude::*;
diff --git a/tests/test_cargo_init.rs b/tests/test_cargo_init.rs
new file mode 100644 (file)
index 0000000..f36ea06
--- /dev/null
@@ -0,0 +1,315 @@
+use std::fs::{self, File};
+use std::io::prelude::*;
+use std::env;
+use tempdir::TempDir;
+use support::{execs, paths, cargo_dir};
+use hamcrest::{assert_that, existing_file, existing_dir, is_not};
+
+use cargo::util::{process, ProcessBuilder};
+
+fn setup() {
+}
+
+fn cargo_process(s: &str) -> ProcessBuilder {
+    let mut p = process(&cargo_dir().join("cargo"));
+    p.arg(s).cwd(&paths::root()).env("HOME", &paths::home());
+    return p;
+}
+
+test!(simple_lib {
+    assert_that(cargo_process("init").arg("--vcs").arg("none")
+                                    .env("USER", "foo"),
+                execs().with_status(0));
+
+    assert_that(&paths::root().join("Cargo.toml"), existing_file());
+    assert_that(&paths::root().join("src/lib.rs"), existing_file());
+    assert_that(&paths::root().join(".gitignore"), is_not(existing_file()));
+
+    assert_that(cargo_process("build"),
+                execs().with_status(0));
+});
+
+test!(simple_bin {
+    let path = paths::root().join("foo");
+    fs::create_dir(&path).unwrap();
+    assert_that(cargo_process("init").arg("--bin").arg("--vcs").arg("none")
+                                    .env("USER", "foo").cwd(&path),
+                execs().with_status(0));
+
+    assert_that(&paths::root().join("foo/Cargo.toml"), existing_file());
+    assert_that(&paths::root().join("foo/src/main.rs"), existing_file());
+
+    assert_that(cargo_process("build").cwd(&path),
+                execs().with_status(0));
+    assert_that(&paths::root().join(&format!("foo/target/debug/foo{}",
+                                             env::consts::EXE_SUFFIX)),
+                existing_file());
+});
+
+fn bin_already_exists(explicit: bool, rellocation: &str) {
+    let path = paths::root().join("foo");
+    fs::create_dir_all(&path.join("src")).unwrap();
+    
+    let sourcefile_path = path.join(rellocation);
+    
+    let content = br#"
+        fn main() {
+            println!("Hello, world 2!");
+        }
+    "#;
+    
+    File::create(&sourcefile_path).unwrap().write_all(content).unwrap();
+    
+    if explicit {
+        assert_that(cargo_process("init").arg("--bin").arg("--vcs").arg("none")
+                                        .env("USER", "foo").cwd(&path),
+                    execs().with_status(0));
+    } else {
+        assert_that(cargo_process("init").arg("--vcs").arg("none")
+                                        .env("USER", "foo").cwd(&path),
+                    execs().with_status(0));
+    }
+
+    assert_that(&paths::root().join("foo/Cargo.toml"), existing_file());
+    assert_that(&paths::root().join("foo/src/lib.rs"), is_not(existing_file()));
+    
+    // Check that our file is not overwritten
+    let mut new_content = Vec::new();
+    File::open(&sourcefile_path).unwrap().read_to_end(&mut new_content).unwrap();
+    assert_eq!(Vec::from(content as &[u8]), new_content);
+}
+
+test!(bin_already_exists_explicit {
+    bin_already_exists(true, "src/main.rs")
+});
+
+test!(bin_already_exists_implicit {
+    bin_already_exists(false, "src/main.rs")
+});
+
+test!(bin_already_exists_explicit_nosrc {
+    bin_already_exists(true, "main.rs")
+});
+
+test!(bin_already_exists_implicit_nosrc {
+    bin_already_exists(false, "main.rs")
+});
+
+test!(bin_already_exists_implicit_namenosrc {
+    bin_already_exists(false, "foo.rs")
+});
+
+test!(bin_already_exists_implicit_namesrc {
+    bin_already_exists(false, "src/foo.rs")
+});
+
+test!(confused_by_multiple_lib_files {
+    let path = paths::root().join("foo");
+    fs::create_dir_all(&path.join("src")).unwrap();
+    
+    let sourcefile_path1 = path.join("src/lib.rs");
+    
+    File::create(&sourcefile_path1).unwrap().write_all(br#"
+        fn qqq () {
+            println!("Hello, world 2!");
+        }
+    "#).unwrap();
+    
+    let sourcefile_path2 = path.join("lib.rs");
+    
+    File::create(&sourcefile_path2).unwrap().write_all(br#"
+        fn qqq () {
+            println!("Hello, world 3!");
+        }
+    "#).unwrap();
+    
+    assert_that(cargo_process("init").arg("--vcs").arg("none")
+                                    .env("USER", "foo").cwd(&path),
+                execs().with_status(101).with_stderr("\
+cannot have a project with multiple libraries, found both `src/lib.rs` and `lib.rs`
+"));
+    
+    assert_that(&paths::root().join("foo/Cargo.toml"), is_not(existing_file()));
+});
+
+
+test!(multibin_project_name_clash {
+    let path = paths::root().join("foo");
+    fs::create_dir(&path).unwrap();
+    
+    let sourcefile_path1 = path.join("foo.rs");
+    
+    File::create(&sourcefile_path1).unwrap().write_all(br#"
+        fn main () {
+            println!("Hello, world 2!");
+        }
+    "#).unwrap();
+    
+    let sourcefile_path2 = path.join("main.rs");
+    
+    File::create(&sourcefile_path2).unwrap().write_all(br#"
+        fn main () {
+            println!("Hello, world 3!");
+        }
+    "#).unwrap();
+    
+    assert_that(cargo_process("init").arg("--vcs").arg("none")
+                                    .env("USER", "foo").cwd(&path),
+                execs().with_status(101).with_stderr("\
+multiple possible binary sources found:
+  main.rs
+  foo.rs
+cannot automatically generate Cargo.toml as the main target would be ambiguous
+"));
+                
+    assert_that(&paths::root().join("foo/Cargo.toml"), is_not(existing_file()));
+});
+
+fn lib_already_exists(rellocation: &str) {
+    let path = paths::root().join("foo");
+    fs::create_dir_all(&path.join("src")).unwrap();
+    
+    let sourcefile_path = path.join(rellocation);
+    
+    let content = br#"
+        pub fn qqq() {}
+    "#;
+    
+    File::create(&sourcefile_path).unwrap().write_all(content).unwrap();
+    
+    assert_that(cargo_process("init").arg("--vcs").arg("none")
+                                    .env("USER", "foo").cwd(&path),
+                execs().with_status(0));
+
+    assert_that(&paths::root().join("foo/Cargo.toml"), existing_file());
+    assert_that(&paths::root().join("foo/src/main.rs"), is_not(existing_file()));
+    
+    // Check that our file is not overwritten
+    let mut new_content = Vec::new();
+    File::open(&sourcefile_path).unwrap().read_to_end(&mut new_content).unwrap();
+    assert_eq!(Vec::from(content as &[u8]), new_content);
+}
+
+test!(lib_already_exists_src {
+    lib_already_exists("src/lib.rs")
+});
+
+test!(lib_already_exists_nosrc {
+    lib_already_exists("lib.rs")
+});
+
+test!(simple_git {
+    assert_that(cargo_process("init").arg("--vcs").arg("git")
+                                    .env("USER", "foo"),
+                execs().with_status(0));
+
+    assert_that(&paths::root().join("Cargo.toml"), existing_file());
+    assert_that(&paths::root().join("src/lib.rs"), existing_file());
+    assert_that(&paths::root().join(".git"), existing_dir());
+    assert_that(&paths::root().join(".gitignore"), existing_file());
+});
+
+test!(auto_git {
+    let td = TempDir::new("cargo").unwrap();
+    let foo = &td.path().join("foo");
+    fs::create_dir_all(&foo).unwrap();
+    assert_that(cargo_process("init").cwd(foo.clone())
+                                     .env("USER", "foo"),
+                execs().with_status(0));
+
+    assert_that(&foo.join("Cargo.toml"), existing_file());
+    assert_that(&foo.join("src/lib.rs"), existing_file());
+    assert_that(&foo.join(".git"), existing_dir());
+    assert_that(&foo.join(".gitignore"), existing_file());
+});
+
+test!(invalid_dir_name {
+    let foo = &paths::root().join("foo.bar");
+    fs::create_dir_all(&foo).unwrap();
+    assert_that(cargo_process("init").cwd(foo.clone())
+                                     .env("USER", "foo"),
+                execs().with_status(101).with_stderr("\
+Invalid character `.` in crate name: `foo.bar`
+use --name to override crate name
+"));
+
+    assert_that(&foo.join("Cargo.toml"), is_not(existing_file()));
+});
+
+test!(git_autodetect {
+    fs::create_dir(&paths::root().join(".git")).unwrap();
+    
+    assert_that(cargo_process("init")
+                                    .env("USER", "foo"),
+                execs().with_status(0));
+    
+
+    assert_that(&paths::root().join("Cargo.toml"), existing_file());
+    assert_that(&paths::root().join("src/lib.rs"), existing_file());
+    assert_that(&paths::root().join(".git"), existing_dir());
+    assert_that(&paths::root().join(".gitignore"), existing_file());
+});
+
+
+test!(mercurial_autodetect {
+    fs::create_dir(&paths::root().join(".hg")).unwrap();
+    
+    assert_that(cargo_process("init")
+                                    .env("USER", "foo"),
+                execs().with_status(0));
+    
+
+    assert_that(&paths::root().join("Cargo.toml"), existing_file());
+    assert_that(&paths::root().join("src/lib.rs"), existing_file());
+    assert_that(&paths::root().join(".git"), is_not(existing_dir()));
+    assert_that(&paths::root().join(".hgignore"), existing_file());
+});
+
+test!(gitignore_appended_not_replaced {
+    fs::create_dir(&paths::root().join(".git")).unwrap();
+    
+    File::create(&paths::root().join(".gitignore")).unwrap().write_all(b"qqqqqq\n").unwrap();
+    
+    assert_that(cargo_process("init")
+                                    .env("USER", "foo"),
+                execs().with_status(0));
+    
+
+    assert_that(&paths::root().join("Cargo.toml"), existing_file());
+    assert_that(&paths::root().join("src/lib.rs"), existing_file());
+    assert_that(&paths::root().join(".git"), existing_dir());
+    assert_that(&paths::root().join(".gitignore"), existing_file());
+    
+    let mut contents = String::new();
+    File::open(&paths::root().join(".gitignore")).unwrap().read_to_string(&mut contents).unwrap();
+    assert!(contents.contains(r#"qqqqqq"#));
+});
+
+test!(with_argument {
+    assert_that(cargo_process("init").arg("foo").arg("--vcs").arg("none")
+                                     .env("USER", "foo"),
+                execs().with_status(0));
+    assert_that(&paths::root().join("foo/Cargo.toml"), existing_file());
+});
+
+
+test!(unknown_flags {
+    assert_that(cargo_process("init").arg("foo").arg("--flag"),
+                execs().with_status(1)
+                       .with_stderr("\
+Unknown flag: '--flag'
+
+Usage:
+    cargo init [options] [<path>]
+    cargo init -h | --help
+"));
+});
+
+#[cfg(not(windows))]
+test!(no_filename {
+    assert_that(cargo_process("init").arg("/"),
+                execs().with_status(101)
+                       .with_stderr("\
+cannot auto-detect project name from path \"/\" ; use --name to override
+"));
+});
index 8c61d05ae279a8b46160680d3b176f10c6c7e175..63868112fcd869018eeab6be21ed96306ead8706 100644 (file)
@@ -94,7 +94,9 @@ test!(existing {
 test!(invalid_characters {
     assert_that(cargo_process("new").arg("foo.rs"),
                 execs().with_status(101)
-                       .with_stderr("Invalid character `.` in crate name: `foo.rs`"));
+                       .with_stderr("\
+Invalid character `.` in crate name: `foo.rs`
+use --name to override crate name"));
 });
 
 test!(rust_prefix_stripped {
index 761fee61bb027bec60817a7165ed7c504b085761..a435f91b860fc77614d91fd8e618e82adea3f193 100644 (file)
@@ -49,6 +49,7 @@ mod test_cargo_features;
 mod test_cargo_fetch;
 mod test_cargo_freshness;
 mod test_cargo_generate_lockfile;
+mod test_cargo_init;
 mod test_cargo_install;
 mod test_cargo_new;
 mod test_cargo_package;